
in vec2				v_TexCoord;

uniform vec3			u_ViewOrigin;
uniform mat4			u_UnprojectMatrix;
uniform mat4			u_LightMatrix;
uniform mat4			u_ShadowMatrix;
uniform vec3			u_LightColor;
uniform vec4			u_LightPlane;
uniform vec4			u_ClipPlanes[8];
uniform vec2			u_DepthScaleAndBias;
uniform vec4			u_LightShaftsParms;

uniform sampler2D		u_LightProjectionMap;
uniform sampler2D		u_LightFalloffMap;
uniform sampler2DShadow		u_ShadowMap;
uniform sampler2D		u_JitterMap;
uniform sampler2D		u_DepthMap;


void main (){

	vec3	light;
	vec4	lightTexCoord;
	vec3	lightShafts;
	float	shadow;
	vec3	shadowTexCoord;
	vec3	traceStart, traceEnd;
	vec3	traceDirection;
	float	traceDistance;
	vec4	samplePosition;
	float	sampleDistance, sampleCount;
	vec4	position;
	float	receiver, depth;
	float	d1, d2;
	int	i;

	// Load the depth from the depth map
	depth = texture(u_DepthMap, v_TexCoord).r * 2.0 - 1.0;

	// Compute the world space position
	position = u_UnprojectMatrix * vec4(v_TexCoord * 2.0 - 1.0, depth, 1.0);

	position.xyz /= position.w;

	// Clip the trace to the given volume
	traceStart = u_ViewOrigin;
	traceEnd = position.xyz;

	for (i = 0; i < 8; i++){
		d1 = dot(u_ClipPlanes[i], vec4(traceStart, 1.0));
		d2 = dot(u_ClipPlanes[i], vec4(traceEnd, 1.0));

		if (d1 >= 0.0 && d2 >= 0.0)
			continue;

		if (d1 < 0.0 && d2 < 0.0)
			discard;

		if (d1 < 0.0)
			traceStart = mix(traceStart, traceEnd, d1 / (d1 - d2));
		else
			traceEnd = mix(traceStart, traceEnd, d1 / (d1 - d2));
	}

	// Compute trace direction
	traceDirection = normalize(traceEnd - traceStart);

	// Compute trace distance
	traceDistance = max(distance(traceEnd, traceStart), 50.0);

	// Trace along direction
	lightShafts = vec3(0.0);

	sampleCount = 0.0;
	sampleDistance = texture(u_JitterMap, gl_FragCoord.xy * u_LightShaftsParms.y).r * u_LightShaftsParms.z;

	if (u_LightShaftsParms.x == 0.0){
		// Unshadowed light shafts
		while (sampleDistance < traceDistance){
			samplePosition = vec4(traceStart + traceDirection * sampleDistance, 1.0);

			sampleCount += 1.0;
			sampleDistance += u_LightShaftsParms.z;

			// Compute the light projection/falloff map texture coord
			lightTexCoord.s = dot(samplePosition, u_LightMatrix[0]);
			lightTexCoord.t = dot(samplePosition, u_LightMatrix[1]);
			lightTexCoord.p = dot(samplePosition, u_LightMatrix[2]);
			lightTexCoord.q = dot(samplePosition, u_LightMatrix[3]);

			// Compute light attenuation
			light = textureProj(u_LightProjectionMap, lightTexCoord.stq).rgb * texture(u_LightFalloffMap, vec2(lightTexCoord.p, 0.5)).rgb;

			// Accumulate lighting contributions
			lightShafts += light;
		}
	}
	else {
		// Shadowed light shafts
		while (sampleDistance < traceDistance){
			samplePosition = vec4(traceStart + traceDirection * sampleDistance, 1.0);

			sampleCount += 1.0;
			sampleDistance += u_LightShaftsParms.z;

			// Compute the light projection/falloff map texture coord
			lightTexCoord.s = dot(samplePosition, u_LightMatrix[0]);
			lightTexCoord.t = dot(samplePosition, u_LightMatrix[1]);
			lightTexCoord.p = dot(samplePosition, u_LightMatrix[2]);
			lightTexCoord.q = dot(samplePosition, u_LightMatrix[3]);

			// Compute light attenuation
			light = textureProj(u_LightProjectionMap, lightTexCoord.stq).rgb * texture(u_LightFalloffMap, vec2(lightTexCoord.p, 0.5)).rgb;

			// Compute the shadow map texture coord
			shadowTexCoord.s = dot(samplePosition, u_ShadowMatrix[0]);
			shadowTexCoord.t = dot(samplePosition, u_ShadowMatrix[1]);
			shadowTexCoord.p = dot(samplePosition, u_ShadowMatrix[3]);

			// Compute distance to receiver
			receiver = dot(samplePosition, u_LightPlane);

			// Transform into screen space
			depth = 0.5 + 0.5 * ((receiver * u_DepthScaleAndBias.x + u_DepthScaleAndBias.y) / receiver);

			// Sample the shadow map
			shadow = texture(u_ShadowMap, vec3(shadowTexCoord.st / shadowTexCoord.p, depth));

			// Accumulate lighting and shadowing contributions
			lightShafts += light * shadow;
		}
	}

	// Scale the intensity
	if (sampleCount == 0.0)
		lightShafts = vec3(0.0);
	else
		lightShafts = (lightShafts / sampleCount) * u_LightShaftsParms.w;

	// Write the final color modulated by the light color
	gl_FragColor.rgb = lightShafts * u_LightColor;
}